/*
The MIT License (MIT)

Copyright (c) 2013 Mike Dapiran, Brian May, Richard Pospesel, and Bert Wierenga

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software 
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#include "../Hogshead.Core.h"

#include "IGame.h"
#include "../Content/Prefab.h"
#include "../Content/Scene.h"
#include "../Managers/ComponentIndex.h"
#include "../Managers/ContentLibrary.h"
#include "../Managers/DirectorIndex.h"
#include "../Managers/Factory.h"
#include "../Managers/Messenger.h"

#include <crtdbg.h>

namespace Hogshead
{
	namespace Core
	{
		IGame* IGame::_game = NULL;
		String IGame::_previous_scene_path = "";

		IGame::IGame() : _reload_scene(false), _change(false), _transist(false)
		{
			_current_scene = NULL;
			_game_timer = NULL;
			_hWnd = NULL;
			_system_paused = false;

			IGame::_game = this;
			IGame::_game->_transist = false;

			_last_draw_time = 0.0f;
			_multithread_update = false;
			_should_quit = false;
			_target_framerate = 60;

			
		}

		IGame::~IGame()
		{
			if( _game_timer )
			{
				delete _game_timer;
				_game_timer = 0;
			}
		}

		bool IGame::initialize(int in_width, int in_height)
		{
			//do config first
			configure();

			if(createWindow( GetModuleHandle(NULL), in_width, in_height))
			{
				_has_finalized = false;

				

				// initialize the core engine
				Hogshead::Common::initialize();
				Hogshead::Core::Factory::initialize();
				//Hogshead::Core::Messenger::initialize();
				Hogshead::Core::Messenger::initialize();
				Hogshead::Core::ComponentIndex::initialize();
				Hogshead::Core::DirectorIndex::initialize();

				// register core content
				ContentLibrary::registerContentType<Scene>();
				ContentLibrary::registerContentType<Prefab>();

				// initialize engine libraries (rendering, etc)
				initializeEngineLibraries();

				initializeGameplayLibraries();
				
				// this needs to happen last
				Hogshead::Core::ContentLibrary::initialize();

				// load content
				loadContent();
				_game_timer = new GameTimer();
				_game_timer->startTimer();

				// load and start scene
				selectStartScene();

				return true;
			}
			return false;
		}

		bool IGame::initialize(HWND in_window_handle)
		{
			//do config first
			configure();

			_has_finalized = false;
			this->_hWnd = in_window_handle;			

			// initialize the core engine
			Hogshead::Common::initialize();
			Hogshead::Core::Factory::initialize();
			//Hogshead::Core::Messenger::initialize();
			Hogshead::Core::Messenger::initialize();
			Hogshead::Core::ComponentIndex::initialize();
			Hogshead::Core::DirectorIndex::initialize();

			// register core content
			ContentLibrary::registerContentType<Scene>();
			ContentLibrary::registerContentType<Prefab>();

			// initialize engine libraries (rendering, etc)
			initializeEngineLibraries();

			initializeGameplayLibraries();
			// this needs to happen last
			Hogshead::Core::ContentLibrary::initialize();

			// load content
			loadContent();
			_game_timer = new GameTimer();
			_game_timer->startTimer();

			// load and start scene
			selectStartScene();

			return true;
		}
#ifdef _DEBUG
	#define HEAP_CHECK_ASSERT _ASSERTE( _CrtCheckMemory( ) );
#else
	#define HEAP_CHECK_ASSERT
#endif
		void IGame::finalize()
		{
			HEAP_CHECK_ASSERT
			finalizeGameplayLibraries();

			HEAP_CHECK_ASSERT
			finalizeEngineLibraries();

			HEAP_CHECK_ASSERT
			Hogshead::Core::ContentLibrary::finalize();

			HEAP_CHECK_ASSERT
			Hogshead::Core::DirectorIndex::finalize();

			HEAP_CHECK_ASSERT
			Hogshead::Core::ComponentIndex::finalize();

			HEAP_CHECK_ASSERT
			Hogshead::Core::Messenger::finalize();

			HEAP_CHECK_ASSERT
			Hogshead::Core::Factory::finalize();

			HEAP_CHECK_ASSERT
			Hogshead::Common::finalize();

			HEAP_CHECK_ASSERT
		}

		Scene* IGame::getCurrentScene() const
		{
			return _current_scene;
		}

		void IGame::exit()
		{
			_game->_should_quit = true;
		}

		bool IGame::getCurrentSceneKey(String& out_key )
		{

			if( IGame::_game->_current_scene )
			{
				out_key = IGame::_game->_current_scene->getKey();
				return true;			
			}

			return false;
		}

		const String& IGame::loadScene(const String& in_filepath, bool prepend_environment_path)
		{
			//load the Scene via content library
			Scene* scene = (Scene*) ContentLibrary::loadAndParseContentXML(in_filepath, prepend_environment_path);
			
			//if it's already been loaded, initialized, or ran then we need to reload it because this tells us
			//we were returned a scene that we already loaded.

			if( scene->_scene_processes_completed & (LOADED | INITIALIZED | HAS_BEEN_RUN) )
			{
				String key = scene->getKey();
				IGame::_game->safeReloadScene(key);
				scene = ContentLibrary::getScene(key);
				//return the key
				return scene->_key;
			}
			else
			{
				scene->_scene_processes_completed |= LOADED;
				scene->initialize();
				scene->_scene_processes_completed |= INITIALIZED;
				scene->_parent_game = IGame::_game;
				//return the key
				return scene->_key;
			}
		}

		bool IGame::unloadScene(const String& in_key)
		{
			bool null_out = false;
			if( IGame::_game->_current_scene && 
				in_key.equals(IGame::_game->_current_scene->_key) )
			{
				null_out = true;
			}

			//use the key to get the Scene from the ContentLibrary and then release it back to 
			// the ContentLibrary directly.
			ContentLibrary::release( ContentLibrary::getScene(in_key) );

			if( null_out )
				IGame::_game->_current_scene = NULL;

			return null_out;
		}

		

		void IGame::changeScene(const String& in_filepath)
		{
			IGame::_game->_new_scene_filepath = in_filepath;
			IGame::_game->_change = true;
		}

		void IGame::transistScene(const String& in_filepath)
		{
			IGame::_game->_new_scene_filepath = in_filepath;
			IGame::_game->_transist = true;
		}

		void IGame::reloadScene(const String& in_key)
		{
			IGame::_game->_reload_scene_key = in_key;
			IGame::_game->_reload_scene = true;
		}

		void IGame::safeReloadScene(const String& in_key)
		{

			Scene* scene = ContentLibrary::getScene(in_key);
			if(scene)
			{
				//get the path so we can reload it
				String scene_path = scene->getPath();

				//unload it
				IGame::unloadScene(scene->getKey());


				//reload it
				String new_key = IGame::loadScene(scene_path, false);


				IGame::_game->_current_scene = ContentLibrary::getScene( new_key );
				IGame::_game->_reload_scene = false;
			}
			else
			{
				error("attempting to reload a scene that is not loaded");
			}
		}

		void IGame::safeTransistToScene(const String& in_filepath)
		{
			//get current scene name
			String current_name = _game->getCurrentScene()->_key;

			//unload old scene
			unloadScene(current_name);
							
			//load new scene
			String new_key = loadScene(in_filepath);

			//purge content no longer referenced
			ContentLibrary::purge();
	
			//switch to new scene
			Scene* scene = ContentLibrary::getScene(new_key);
			if(scene)
			{

				IGame::_game->_current_scene = scene;
				IGame::_game->_current_scene->resume();
				_previous_scene_path = ContentLibrary::getRelativePath(scene->getPath());

				//_previous_scene_path = IGame::getCurrentScene()->getPath();
			}
			IGame::_game->_transist = false;
			IGame::_game->_game_timer->updateTick();
			IGame::_game->_game_timer->updateTick();

			IGame::_game->_game_timer->drawTick();
			IGame::_game->_game_timer->drawTick();
		}

		void IGame::safeChangeToScene(const String& in_filepath)
		{
			//load new scene
			String new_key = loadScene(in_filepath);
	
			//switch to new scene
			Scene* scene = ContentLibrary::getScene(new_key);
			if(scene)
			{
				
				IGame::_game->_current_scene = scene;
				IGame::_game->_current_scene->resume();
				_previous_scene_path = ContentLibrary::getRelativePath(scene->getPath());

				//_previous_scene_path = IGame::getCurrentScene()->getPath();
			}
			IGame::_game->_change = false;
			IGame::_game->_game_timer->updateTick();
			IGame::_game->_game_timer->updateTick();

			IGame::_game->_game_timer->drawTick();
			IGame::_game->_game_timer->drawTick();
		}

		void IGame::run()
		{
			//hout << "run" << endl;
			MSG msg = {0};

			_game_timer->startTimer();

			float time_since_last_query = 0.0f;

			while(msg.message != WM_QUIT)
			{
				if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
				{
					TranslateMessage( &msg );
					DispatchMessage( &msg );
				}
				else
				{
					if(_should_quit)
					{
						break;
					}
					else
					{
						//check for system paused
						if(_system_paused)
						{
							Parallel::sleep(10);
							continue;
						}

						//debug fps BS
						if(time_since_last_query > 0.1f)
						{
							time_since_last_query = 0.0f;
							//hout << "Framerate: " << _game_timer->getFPS() << " fps" << '\r';
						}
						time_since_last_query += _game_timer->getUpdateDelta();

						//update

						//draw check
						if( _target_framerate > 0 && _game_timer->getGameTime()-_last_draw_time <= 1.0f/(float)_target_framerate) // framerate cap
							_game_timer->setIsDrawCycle(false);
						else
							_game_timer->setIsDrawCycle(true);

						update(*_game_timer);

						_game_timer->updateTick();

						//scene transition
						if( _transist )
						{
							safeTransistToScene( _new_scene_filepath );
							continue;
						}
						//scene change
						else if( _change )
						{
							safeChangeToScene( _new_scene_filepath );
							continue;
						}
						else if( _reload_scene )
						{
							safeReloadScene( _reload_scene_key );
							continue;
						}

						if(_game_timer->isDrawCycle())
						{
							_current_scene->_scene_phase = ScenePhase::Draw;
							draw();
							_game_timer->drawTick();
							_last_draw_time = _game_timer->getGameTime();
						}
						_current_scene->_scene_phase = ScenePhase::None;
					}
				}
			}

			quit();
		}

		void IGame::pauseSystem()
		{
			_system_paused = true;
			if(_current_scene)
				_current_scene->pause();
			_game_timer->pause();
		}

		void IGame::unPauseSystem()
		{
			_system_paused = false;

			if(_current_scene)
				_current_scene->resume();
			_game_timer->unPause();
		}

		void IGame::quit()
		{
			if(_has_finalized)
				return;

			finalize();
			PostQuitMessage(0);
			_has_finalized = true;
		}

		void IGame::update(const GameTimer& in_timer)
		{
			if(_current_scene != NULL)
			{
				_current_scene->update(in_timer);
				_current_scene->_scene_processes_completed |= HAS_BEEN_RUN;
				_current_scene->_scene_phase = ScenePhase::MessageHandling;
				Messenger::handleMessages();
			}
		}

		void IGame::draw()
		{
			for(int i = 0; i < _manager_predraw_callback.size(); i++)
				(*_manager_predraw_callback[i])();

			if(_current_scene != NULL)
				_current_scene->draw();
		}

#pragma region WINDOWS
		void IGame::registerWindowsEventCallback(WNDPROC in_proc)
		{
			_manager_callback.add( in_proc );
		}
		
		LRESULT CALLBACK IGame::wndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
		{
			static IGame* g_the_game = 0;

			if( message == WM_CREATE )
			{
				CREATESTRUCT* cs = (CREATESTRUCT*)lParam;
				g_the_game = (IGame*)cs->lpCreateParams;
				return 0;
			}
			if(g_the_game)
				return g_the_game->handleWindowsMessage(hWnd, message, wParam, lParam);
			else
				return DefWindowProc(hWnd, message, wParam, lParam);
		}
		
		LRESULT CALLBACK IGame::handleWindowsMessage(HWND p_window_handle, UINT p_message, WPARAM p_word, LPARAM p_long_word)
		{
			switch (p_message) 
			{
				// Allow the user to press the escape key to end the application
				case WM_KEYDOWN	:	
					switch(p_word)
						{
							// Check if the user hit the escape key
							case VK_ESCAPE : exit();
							break;
						}	
					break;
				case WM_SIZE:
					switch(p_word)
					{
					case SIZE_MAXHIDE: 
					case SIZE_MINIMIZED: 
						pauseSystem();
						break;
					}
					break;
				case WM_ENTERSIZEMOVE: 
					pauseSystem();
					break;
				case WM_EXITSIZEMOVE: 
					unPauseSystem(); 
					break;


				//check for activity, use this to pause play, may have effects on editor
				case WM_ACTIVATE :
					switch(p_word)
					{
						case WA_INACTIVE : 
							pauseSystem(); 
							break;
						case WA_ACTIVE :
						case WA_CLICKACTIVE : 
							unPauseSystem(); 
							break;
					}
					break;

				////check for the space bar to switch to full screen
				//case WM_CHAR : 
				//	switch(p_word)
				//	{
				//		case ' ' :
				//			break;
				//	}
				//	return 0;

			
				// The user hit the close button, close the application
				//case WM_DESTROY	: 
				case WM_CLOSE: exit(); break;
			}

			//check to see if any manager callback functions care about this message
			LRESULT long_results = 0;

			for(int i = 0; i < _manager_callback.size(); i++)
			{
				long_results |= _manager_callback[i](p_window_handle, p_message, p_word, p_long_word);
			}

			//if they had an issue return it
			if(long_results)
				return long_results;
			//otherwise 
			else
				return DefWindowProc(p_window_handle, p_message, p_word, p_long_word);
		}

		bool IGame::createWindow(HINSTANCE p_hInstance, int p_width, int p_height)
		{
			
			WNDCLASSEX window;

			window.cbSize			= sizeof(WNDCLASSEX);
			window.style			= CS_HREDRAW | CS_VREDRAW;
			window.lpfnWndProc		= (WNDPROC)wndProc;
			window.cbClsExtra		= 0;
			window.cbWndExtra		= 0;
			window.hInstance		= p_hInstance;
			window.hIcon			= 0;
			//window.hCursor		= LoadCursorFromFile("cursor1.cur");
			window.hCursor			= LoadCursor(NULL, IDC_CROSS);
			window.hbrBackground	= (HBRUSH)(COLOR_WINDOW);
			window.lpszMenuName		= NULL;
			window.lpszClassName	= TEXT("Game");
			window.hIconSm			= 0;
			RegisterClassEx(&window);

			//Resize the window
			RECT rect = { 0, 0, p_width, p_height };
			AdjustWindowRect(&rect, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE);

			//create the window from the class above
			//disable resizing and correct for extra width and height
			_hWnd = CreateWindow( "Game", 
								 "Hogshead Engine", 
								 WS_OVERLAPPEDWINDOW,
								 0, 
								 0, 
								 rect.right - rect.left, 
								 rect.bottom - rect.top, 
								 NULL, 
								 NULL, 
								 p_hInstance, 
								 this);
		   
			//window handle not created
			if (!_hWnd) return false;
			
			//if window creation was successful
			ShowWindow(_hWnd, SW_SHOW);
			UpdateWindow(_hWnd);
			
			return true;
		}

		void IGame::getPreviousScenePath(String& out_key)
		{
			out_key = IGame::_previous_scene_path;
		}
#pragma endregion
	}
}